import { Page, JSCoverageEntry } from 'puppeteer-core';
import { TestRunnerCoreConfig } from '@web/test-runner-core';
import { v8ToIstanbul } from '@web/test-runner-coverage-v8';
import { SessionResult } from '@web/test-runner-core';

declare global {
  interface Window {
    __bringTabToFront: (id: string) => void;
    __releaseLock: (id: string) => void;
  }
}

export class ChromeLauncherPage {
  private config: TestRunnerCoreConfig;
  private testFiles: string[];
  private browser: string;
  public puppeteerPage: Page;
  private nativeInstrumentationEnabledOnPage = false;
  private patchAdded = false;
  private resolvers: Record<string, () => void> = {};

  constructor(
    config: TestRunnerCoreConfig,
    testFiles: string[],
    product: string,
    puppeteerPage: Page,
  ) {
    this.config = config;
    this.testFiles = testFiles;
    this.browser = product;
    this.puppeteerPage = puppeteerPage;
  }

  async runSession(url: string, coverage: boolean) {
    if (
      coverage &&
      this.config.coverageConfig?.nativeInstrumentation !== false &&
      this.browser === 'chromium'
    ) {
      if (this.nativeInstrumentationEnabledOnPage) {
        await this.puppeteerPage.coverage.stopJSCoverage();
      }
      this.nativeInstrumentationEnabledOnPage = true;
      await this.puppeteerPage.coverage.startJSCoverage({
        includeRawScriptCoverage: true,
      });
    }

    await this.puppeteerPage.setViewport({ height: 600, width: 800 });
    await this.puppeteerPage.goto(url);
  }

  async stopSession(): Promise<SessionResult> {
    const testCoverage = await this.collectTestCoverage(this.config, this.testFiles);

    // navigate to an empty page to kill any running code on the page, stopping timers and
    // breaking a potential endless reload loop
    await this.puppeteerPage.goto('about:blank');

    return { testCoverage };
  }

  private async collectTestCoverage(config: TestRunnerCoreConfig, testFiles: string[]) {
    const userAgentPromise = this.puppeteerPage
      .browser()
      .userAgent()
      .catch(() => undefined);

    try {
      const coverageFromBrowser = await this.puppeteerPage.evaluate(
        () => (window as any).__coverage__,
      );

      if (coverageFromBrowser) {
        // coverage was generated by JS, return that
        return coverageFromBrowser;
      }
    } catch (error) {
      // evaluate throws when the test navigates in the browser
    }

    if (config.coverageConfig?.nativeInstrumentation === false) {
      throw new Error(
        'Coverage is enabled with nativeInstrumentation disabled. ' +
          'Expected coverage provided in the browser as a global __coverage__ variable.' +
          'Use a plugin like babel-plugin-istanbul to generate the coverage, or enable native instrumentation.',
      );
    }

    if (!this.nativeInstrumentationEnabledOnPage) {
      return undefined;
    }

    const [userAgent, coverageResult] = await Promise.all([
      userAgentPromise,
      this.puppeteerPage.coverage?.stopJSCoverage(),
    ]);
    const v8Coverage = coverageResult
      ?.map(entry => entry.rawScriptCoverage)
      .filter((cov): cov is Required<JSCoverageEntry>['rawScriptCoverage'] => cov !== undefined);
    this.nativeInstrumentationEnabledOnPage = false;

    return v8ToIstanbul(config, testFiles, v8Coverage, userAgent);
  }
}
